Objectives:

The objective of this project is to identify the most problematic devices in a cloud-based telemetry system.
The analysis will have to satisfy the following requirements:

  • Produce an analysis and report of devices uptime for 24h in 15 min intervals
  • For devices with an uptime below 97%, report the 2 most common alarms.
  • Uptime definition: \[ uptime = 100 * (1 - \frac{int.\ with \ NA + int. \ with \ alarms - int.\ with \ maintenance \ alarms}{total \ active \ intervals}) \]
  • Source SQL data

    1. Import the “technicall_assessment.sql” into a SQL database
    2. Pull the SQL tables into into R dataframes
    3. Operate within R as on a dataframe
    # Pulling data from SQL
    # Establish a connection to MySQL
    con <- dbConnect(MySQL(), user = "root", password = "1234", dbname = "telemetry_data", host = "localhost")
    
    # Query the data
    devices_gateway_1 <- dbGetQuery(con, "SELECT * FROM devices_gateway_1")
    devices_gateway_2 <- dbGetQuery(con, "SELECT * FROM devices_gateway_2")
    devices_gateway_3 <- dbGetQuery(con, "SELECT * FROM devices_gateway_3")
    gateway_1 <- dbGetQuery(con, "SELECT * FROM gateway_1")
    gateway_2 <- dbGetQuery(con, "SELECT * FROM gateway_2")
    gateway_3 <- dbGetQuery(con, "SELECT * FROM gateway_3")
    
    # Close the connection
    dbDisconnect(con)
    [1] TRUE

    The database telemetry_data contains six tables. I import them all. From the instructions given I assume only devices_gateway (1,2,3) are relevant.
    Data has 1 minute frequency by device.
    Objective 1: Create an uptime variable aggregated in 15 minutes intervals.

    Data manipulation

    Two approaches:

    1. Work on the single tables: lighter and faster but more coding demanding
    2. Join tables into a single df: slower but less coding demanding
      1. Steps:

        1. Create a gateway-device ID
        2. Data enter frm different point of access. Some device_id are duplicated in devices_gateway_1 and 2.
          Thus, following approach 1, I create a gateway_device_id <- ID. Also change date format and order datasets by ID and time.

          # Create a device identifier by gateway: as devices are repeated across gateways
          
          # Create a list of datasets
          datasets <- list(devices_gateway_1, devices_gateway_2, devices_gateway_3)
          
          # Loop through each dataset
          for (i in seq_along(datasets)) {
            # Extract gateway number
            gateway_number <- i
          
            # Add ID column on every dataset
            datasets[[i]] <- mutate(datasets[[i]], ID = paste(gateway_number, device_id, sep = "_")) %>%
              relocate(ID, .after = utc_datetime)
          
            # Change date format
            datasets[[i]]$utc_datetime <- as.POSIXct(datasets[[i]]$utc_datetime)
          
            # Order entries by date and ID
            datasets[[i]] <- datasets[[i]] %>%
              arrange(ID, utc_datetime)
          }

          Next, for easiness, I join the three tables into a single df.

          # Combine the processed datasets into a single dataframe to simplify visualization and manipulation
          combined_dataset <- do.call(rbind, datasets)
          # Select relevant dataset
          data <- combined_dataset
        3. Check the date structure
        4. The joined dataframe contains 264 devices. Each device should collect data with 1 minute frequency.

          # number f devices
          length(unique(data$ID))
          [1] 264

          There are breaks in the utc_datetime variable. Some devices “skip” some minutes.
          As missing data are relevant for uptime calculation, I introduce empty lines for the missing points of time by ID groups .

          # Step 1: Identify the complete range of timestamps for each device_id
          # It creates a list of timestamps for the 267 devices_gateway
          complete_ranges <- by(data, data$ID, function(x) {
            min_time <- min(x$utc_datetime)
            max_time <- max(x$utc_datetime)
            seq(min_time, max_time, by = "min")
          })
          
          # Step 2: Create a template data frame with all timestamps within the identified range
          template_df <- do.call(rbind, lapply(complete_ranges, function(x) {
            data.frame(utc_datetime = x)
          }))
          
          # Add device_id to the template data frame
          template_df$ID <- rep(names(complete_ranges), sapply(complete_ranges, length))
          
          # Step 3: Merge the template data frame with original data, filling missing values with NA
          merged_df <- merge(template_df, data, by = c("utc_datetime", "ID"), all.x = TRUE)
          
          # If you want to order the merged data frame by device_id and utc_datetime
          merged_df <- merged_df[order(merged_df$ID, merged_df$utc_datetime), ]
          
          # Now the missing observations I added should contain all nas in columns 3:16
          all_na_rows <- merged_df %>%
            ungroup() %>%
            filter(rowSums(is.na(select(., 3:16))) == 14)
          
          data <- merged_df

          I display the introduced missing points:

          all_na_rows

          Next, I still need a day and interval identifier
          I introduce a ID for the day number since the start of the analysis: 23-10.
          Below is displayed the look of the dataset after this data manipulation.

          # Create a variable for the day number
          data <- data %>%
            mutate(day = as.integer(date(utc_datetime) - min(date(utc_datetime))) + 1) %>%
            relocate(day, .after = utc_datetime)
          
          # Create a variable for the interval number within each day
          data$interval <- 1 + ((hour(data$utc_datetime) * 60 + minute(data$utc_datetime)) %/% 15)
          data <- data %>%
            relocate(interval, .after = utc_datetime)
          
          print(data)

          Dataset descriptives:

          data.frame(
            Variable = c("utc_datetime","interval","day","ID","device_id","Alarms1","Alarms2","PanelVoltage_mV" , "Position_a1_rad" , "MotorCurrent_a1_mA" ,"MotorCurrentPeak_a1_mA", "TargetAngle_a1_rad", "PanelCurrent_mA" ,    "MaxError","StateOfCharge"  ,    "Voltage_mV"   ,  "StateOfHealth",      "MainState",      "SafePositionState"),
            Nr_Obs = c(2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823,2676823),
            Min = c("2023-10-23 00:00:00.00",1,1,"1_1",1,0,0,0,-0.9,0,0,-0.9,0,0,0,0,0,0,0),
            Max = c("2023-10-29 23:59:00.00",96,7,"3_127",137,16384,260,49025,0.9,2761,3391,0.9,849,255,100,49025,0,2,1)
          )
        5. Uptime variable calculation
        6. \[ uptime = 100 * (1 - \frac{int.\ with \ NA + int. \ with \ alarms - int.\ with \ maintenance \ alarms}{total \ active \ intervals}) \] According to the info received, uptime can be calculated in multiple ways.
          As it is calculated on 15 minutes intervals and alarms happen at the minute-level, within an interval of 15 minutes there can be different combinations of alarms.
          For instance, we may set alarm = 1 if there was at least 1 alarm within the 15 minutes interval. However, this has strong implications for uptime calculations and almost no device would have a unptime <97% (I provide the code below for this, but do not use this definition).
          An alternative, is to set alarms = 1 if a minimum number of alarms happens within each interval. I use a total of 15 alarms per device-interval (if Alarms1+Alarms2 >= 15–> Alarms = 1) as it is approximately the half alarms number we can have on an interval. This is discretionary and can be modified.

          # Aggregate by ID and interval
          # 2 options for the calculation of alarms: 1) if any alarm-->TRUE; 2) if more than half alarms--> TRUE
          
          # option 1)
          aggregated_data_NO <- data %>%
            group_by(ID, day, interval) %>%
            summarise(
              alarms = any(Alarms1 != 0 | Alarms2 != 0,na.rm=T),
              maintenance_alarm = any(Alarms1 == 16,na.rm=T),
              missing = all(is.na(PanelVoltage_mV) & is.na(Position_a1_rad) & is.na(MotorCurrent_a1_mA) & is.na(MotorCurrentPeak_a1_mA) & is.na(TargetAngle_a1_rad) & is.na(PanelCurrent_mA) & is.na(MaxError) & is.na(StateOfCharge) & is.na(Voltage_mV) & is.na(StateOfHealth) & is.na(MainState) & is.na(SafePositionState))
            )
          `summarise()` has grouped output by 'ID', 'day'. You can override using the `.groups` argument.
          # option 2):
          aggregated_data <- data %>%
            group_by(ID, day, interval) %>%
            summarise(
              alarms = sum(Alarms1 != 0 | Alarms2 != 0, na.rm=T) >= 15, #counts instances of alarm 1 or 2 different from zero, if >= 15 TRUE, o.w FALSE
              maintenance_alarm = any(Alarms1 == 16, na.rm = T), # if any alarm1=16 TRUE, o.w FALSE
              missing = all(is.na(PanelVoltage_mV) & is.na(Position_a1_rad) & is.na(MotorCurrent_a1_mA) & is.na(MotorCurrentPeak_a1_mA) & is.na(TargetAngle_a1_rad) & is.na(PanelCurrent_mA) & is.na(MaxError) & is.na(StateOfCharge) & is.na(Voltage_mV) & is.na(StateOfHealth) & is.na(MainState) & is.na(SafePositionState))#if all vars is.na TRUE, FALSE o.w.
            )
          `summarise()` has grouped output by 'ID', 'day'. You can override using the `.groups` argument.
          print(aggregated_data)

          Dataframe for uptime calculation:

          #option 1) 
          uptime_components_NO <- aggregated_data_NO %>%
            group_by(ID) %>%
            summarise(
              total_intervals = n(),
              missing_intervals = sum(missing, na.rm = T),
              alarm_intervals = sum(alarms, na.rm = T),
              maintenance_alarm_intervals = sum(maintenance_alarm, na.rm = T)
            ) %>%
            mutate(
              uptime = 100 * (1 - ((missing_intervals + alarm_intervals - maintenance_alarm_intervals) / total_intervals))
            )
          #option 2)
          uptime_components <- aggregated_data %>%
            group_by(ID) %>%
            summarise(
              total_intervals = n(),
              missing_intervals = sum(missing, na.rm = T),
              alarm_intervals = sum(alarms, na.rm = T),
              maintenance_alarm_intervals = sum(maintenance_alarm, na.rm = T)
            ) %>%
            mutate(
              downtime = 100 * ((missing_intervals + alarm_intervals - maintenance_alarm_intervals) / total_intervals)
            ) %>%
            mutate(uptime = 100 - downtime)
          
          uptime_components

          Here I show how different definitions of uptime can lead to different situations.

          # Create a barplot or histogram
          p0 <- ggplot(uptime_components_NO, aes(x = factor(uptime < 97), fill = factor(uptime < 97))) +
            geom_bar() +
            geom_text(stat = "count", aes(label = ..count..), vjust = -0.5, size = 3) + # Add labels on top of bars
            xlab("Uptime < 97%") + # Set x-axis label
            ylab("Count") + # Set y-axis label
            theme_clean() +
            theme(
              legend.position = "none",
              plot.title = element_text(hjust = 0.5, size = 16), # Center and increase title size
              axis.title.x = element_text(size = 12),
              axis.title.y = element_text(size = 12)
            ) + # Adjust size of x-axis label
            ggtitle("Uptime distribution - Definition 1 (disregarded)")
          
          p1 <- ggplot(uptime_components, aes(x = factor(uptime < 97), fill = factor(uptime < 97))) +
            geom_bar() +
            geom_text(stat = "count", aes(label = ..count..), vjust = -0.5, size = 3) + # Add labels on top of bars
            xlab("Uptime < 97%") + # Set x-axis label
            ylab("Count") + # Set y-axis label
            theme_clean() +
            theme(
              legend.position = "none",
              plot.title = element_text(hjust = 0.5, size = 16), # Center and increase title size
              axis.title.x = element_text(size = 12),
              axis.title.y = element_text(size = 12)
            ) + # Adjust size of x-axis label
            ggtitle("Uptime distribution - Definition 2 (adopted)")
          
          p0 + p1

          Adopting the second definition, across the 3 gateways, there are 264 devices divided by uptime values above or below 97.
          Below I show uptime distribution.
          Aside from an outlier (device 3_121), uptime is concentrated between 80% and 99%, with median clos to 99%.

          # Plot uptime distribution
          ggplot(data = uptime_components, aes(x = "", y = uptime)) +
            geom_boxplot(fill = "skyblue", color = "black") + # Customize boxplot appearance
            xlab("Uptime") + # Remove x-axis label
            ylab("(%)") + # Set y-axis label
            theme_clean() + # Apply minimal theme
            theme(
              panel.grid.major = element_blank(), # Remove major gridlines
              panel.grid.minor = element_blank()
            ) + # Remove minor gridlines
            ggtitle("Uptime Distribution (n=264 devices)") # Add title

          table <- data.frame(
            Uptime = c("<97%", ">= 97%"),
            Devices_number = c(sum(uptime_components$uptime < 97), sum(uptime_components$uptime >= 97))
          )
          
          total <- c(
            "Total",
            sum(as.numeric(table$Devices_number))
          )
          table <- rbind(table, total)
        7. Identify devices with uptime < 97%
        8. table
          # Select devices with uptime<97%
          devices_bad <- uptime_components %>%
            filter(uptime < 97) %>%
            pull(ID)
          
          
          # Filter data based on bad IDs
          devices_bad <- data %>%
            filter(ID %in% devices_bad) %>%
            filter(!is.na(ID))
        9. For devices with uptime < 97%, identify most frequent alarms
        10. We have 2 alarms variables, I identify the two most frequent alarms on each of the variables by aggregating the dataset with uptime<97% devices by two new variables.
          They represent the most 2 frequent alarms for Alarms1 and Alarms2 and I call them top_al1, top_al2.
          IMPORTANT: I disregard Alarms_1 or Alarms_2 = 0 (as they do not constitute alarm code).
          You can see that for Alarms_2 in most cases the most frequent and unique alarm code is Alarms_2 = 0. In those cases top_al2 is NA.

          # Group by ID and summarize the two most frequent non-zero values for Alarms1 and Alarms2
          aggregated_data <- devices_bad %>%
            group_by(ID) %>%
            summarise(
              top_al1 = names(sort(table(Alarms1[Alarms1 != 0]), decreasing = TRUE)[1:2]),
              top_al2 = {
                non_zero_alarms2 <- Alarms2[Alarms2 != 0]
                if (length(unique(non_zero_alarms2)) >= 2) {
                  names(sort(table(non_zero_alarms2), decreasing = TRUE)[1:2])
                } else {
                  NA
                }
              }
            )
          `summarise()` has grouped output by 'ID'. You can override using the `.groups` argument.
          aggregated_data
          # Calculate the frequency of each alarm type by device ID
          alarm_frequency_1 <- devices_bad %>%
            group_by(ID, Alarms1) %>%
            count(name = "Alarms1_frequency") %>%
            filter(!Alarms1 == 0)
          # Restrict to the most frequent two alarms
          top_alarms1 <- alarm_frequency_1 %>%
            group_by(ID, Alarms1) %>%
            summarise(total_frequency = sum(Alarms1_frequency)) %>%
            arrange(desc(total_frequency)) %>%
            slice(1:2)
          `summarise()` has grouped output by 'ID'. You can override using the `.groups` argument.
          # Calculate the frequency of each alarm type by device ID
          alarm_frequency_2 <- devices_bad %>%
            group_by(ID, Alarms2) %>%
            count(name = "Alarms2_frequency") %>%
            filter(!Alarms2 == 0)
          
          # Restrict to the most frequent two alarms
          top_alarms2 <- alarm_frequency_2 %>%
            group_by(ID, Alarms2) %>%
            summarise(total_frequency = sum(Alarms2_frequency)) %>%
            arrange(desc(total_frequency)) %>%
            slice(1:2)
          `summarise()` has grouped output by 'ID'. You can override using the `.groups` argument.
          # Adjust single instances. 3_121 is a particular case because is the only case it has 2 top alarms from Alarms2
          # Remove one observation from top_alarms1
          top_alarms1 <- top_alarms1 %>%
            filter(!(Alarms1 == 128 & ID == "3_121"))
          
          # Remove one observation from top_alarms2
          top_alarms2 <- top_alarms2 %>%
            filter(!(Alarms2 == 4 & ID == "3_121"))
          top_alarms1
          top_alarms2

          Next, join top_alarms1 and 2 into a single dataframe. Create a column for top alarm type and for its “order”.
          Order means whether the alarm is the most frequent or the second most frequent, by device.

        merged <- merge(top_alarms1, top_alarms2, by = "ID", all.x = T)
        # identify top alarms
        merged <- merged %>%
          mutate(top_alarms = ifelse(is.na(Alarms2), Alarms1,
            ifelse(total_frequency.x > total_frequency.y, Alarms1, Alarms2)
          ))
        
        merged <- merged %>%
          group_by(ID) %>%
          mutate(order = row_number()) %>%
          arrange(ID, order)
        
        merged_tab <- merged
        colnames(merged_tab) <- c("ID", "Alarms1 (type)", "Frequency Alarm1 (count)", "Alarms2 (type)", "Frequency Alarms 2 (count)", "Most frequent alarm (type)", "Top 1 or 2")
        # View the result
        merged_tab

    Results visualization

    Some descriptive statistics
    92% of most frequent alarms are codes: 8192; 4096.
    CAREFUL: The number of devices with uptime <97% is 127. Thus, the total absolute frequency should be (127*2)=254 (times 2 because we are looking at the 2 most common errors).
    The slight discrepancy with the total value in the table is given by the fact that not all devices have a second most frequent alarm (e.g. some devices may show only codes 0 and another between 8192 or 4096).

    # Calculate absolute frequency of each alarm
    absolute_frequency <- table(merged$top_alarms) # total frequency
    
    # Calculate percentages of each top_alarms factor
    percentages <- absolute_frequency / nrow(merged) * 100 # total frequency percentages
    
    # Create a data frame with percentages and absolute frequencies
    result <- data.frame(
      Top_alarms = as.integer(names(percentages)),
      Absolute_frequency = as.integer(absolute_frequency),
      Percentage = round(as.numeric(percentages), 2)
    )
    
    # Add a total line
    total <- c(
      "Total",
      sum(as.numeric(result$Absolute_frequency)),
      round(sum(as.numeric(result$Percentage)), 0)
    )
    result <- rbind(result, total)
    
    result

    Identifying most problematic devices

    Next plots show:
    1) Devices with the highest number of alarms among those with uptime<97;
    2) Devices with the highest downtime;
    As downtime is not just equal to the number of alarms it is reasonable to have differences between the devices identified in the two plots

    
    freq <- merge(alarm_frequency_1, alarm_frequency_2, by = "ID", all.x = T) # merge frequencies of all alarms in devices with uptime <97%
    freq <- freq %>%
      group_by(ID) %>%
      mutate(total_freq1_2 = sum(Alarms1_frequency, Alarms2_frequency, na.rm = T)) %>% # create a variable for the sum of all alarms( total alarms frequency)
      select(ID, total_freq1_2) %>% # restrict interest columns
      distinct(ID, total_freq1_2) %>% # filter unique entries
      arrange(-total_freq1_2) %>%
      ungroup() %>% # order dataset by descending order
      mutate(total_freq1_2_perc = 100 * (total_freq1_2 / sum(total_freq1_2)))
    total <- c("Total", sum(freq$total_freq1_2), sum(freq$total_freq1_2_perc))
    freq <- rbind(freq, total)
    freq <- freq %>%
      mutate(total_freq1_2 = as.numeric(total_freq1_2)) %>%
      mutate(total_freq1_2_perc = as.numeric(total_freq1_2_perc))
    
    
    plot1 <- uptime_components %>%
      arrange(desc(downtime)) %>%
      slice_head(n = 20) %>%
      ggplot(aes(x = reorder(ID, -downtime), y = downtime, fill = ID)) +
      geom_bar(stat = "identity") +
      geom_text(aes(label = paste0(round(downtime, 1), "%")), vjust = -0.5, size = 2.5) +
      labs(x = "Device ID", y = "Downtime (%)", title = "20 Devices with highest downtime") +
      theme_clean() +
      theme(
        axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
        legend.position = "none"
      )
    
    
    plot2 <- ggplot(freq[1:20, ], aes(x = reorder(ID, -total_freq1_2), y = total_freq1_2, fill = ID)) +
      geom_bar(stat = "identity") +
      geom_text(aes(label = paste0(round(total_freq1_2_perc, 1), "%")), vjust = -0.5, size = 2.5) +
      labs(x = "Device ID", y = "Count = #Alarms 1 + #Alarms 2", title = "20 Devices (uptime <97%) with highest nr. of alarms") +
      theme_clean() +
      theme(
        axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
        legend.position = "none"
      )
    
    plot1 + plot2

    A heatmap-like plot

    This plot shows the most common two alarms by device ID.
    It allows to identify for each device its most common alarm, allowing direct intervention.
    In red is the most frequent alarm per device, in yellow is the second most frequent alarm.

    # This chunk contains transformations necessary to plot a heatma-like plot
    # Convert relevant columns to factors
    merged$ID <- factor(merged$ID)
    merged$top_alarms <- factor(merged$top_alarms)
    
    
    # Create presence matrix
    presence_matrix <- table(merged$ID, merged$top_alarms)
    
    # Convert the presence matrix to a data frame
    presence_df <- as.data.frame.matrix(presence_matrix)
    
    # Reshape data for ggplot
    presence_df <- presence_df %>%
      rownames_to_column(var = "ID") %>%
      pivot_longer(cols = -ID, names_to = "top_alarms", values_to = "Presence")
    
    presence_df <- presence_df %>%
      mutate(order = ifelse(ID %in% merged$ID & top_alarms %in% merged$top_alarms,
        merged$order[match(paste(ID, top_alarms), paste(merged$ID, merged$top_alarms))],
        NA
      ))
    
    # Ensure 'Presence' is treated as a factor
    presence_df$Presence <- factor(presence_df$Presence, levels = c(0, 1), labels = c("Absent", "Present"))
    
    
    
    a <- ggplot(presence_df, aes(x = ID, y = top_alarms, fill = ifelse(is.na(order), "Not observed", ifelse(order == "1", "Top1 Alarm", "Top2 Alarm")))) +
      geom_tile(color = "white") +
      scale_fill_manual(values = c("Not observed" = "white", "Top1 Alarm" = "red", "Top2 Alarm" = "yellow"), name = "Alarms") +
      theme_minimal() +
      labs(x = "ID", y = "Top Alarms", title = "Presence of Top Alarms by ID when uptime < 97%") +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1, size = 5)) +
      geom_hline(yintercept = seq(0.5, nrow(presence_df) + 0.5, by = 1), color = "gray", linetype = "dashed")
    
    # Center the plot
    
    ggplotly(a)
    Avvertimento: `gather_()` was deprecated in tidyr 1.2.0.
    Please use `gather()` instead.

    What associates mostly to Alarm1?

    Alarm1 is the most frequent type of alarm. As a first step in the direction of a predictive maintenance model, it might be interesting understanding which are the variables associated the most to its manifestation.
    This is a very raw and preliminary attempt.

    Boruta & features importance: another way to look at correlations

    Simply put, Boruta variables’ importance explains the degree to which an input contributes to the prediction of the output. More important variables are thus more related to othe output.
    At a first sight, the (battery) State of Charge is the most impactful variable on the manifestation of Alarms1.

    boruta_data<-data
    #Drop NA
    boruta_data<- na.omit(boruta_data)
    
    #Transform alarm1 into a binary variable: If = NO ALARM; != 0 any other alarm
    boruta_data<-boruta_data%>%
      mutate(Alarms1 = as.factor(ifelse(Alarms1 == 0, 0, 1)),
             MainState = as.factor(MainState),
             StateOfHealth = as.factor(StateOfHealth),
             SafePositionState = as.factor(SafePositionState)
      )
             
    
    #Select relevant variaable for inputs
    boruta_data<-boruta_data[,c(6,8:dim(boruta_data)[2])]
    #Boruta selector
    set.seed(1)
    boruta <- Boruta(boruta_data$Alarms1 ~ . ,data= boruta_data,  doTrace = 2, maxRuns = 100)
     1. run of importance source...
     2. run of importance source...
     3. run of importance source...
     4. run of importance source...
     5. run of importance source...
     6. run of importance source...
     7. run of importance source...
     8. run of importance source...
     9. run of importance source...
     10. run of importance source...
     11. run of importance source...
    After 11 iterations, +5.2 mins: 
     confirmed 11 attributes: MainState, MaxError, MotorCurrent_a1_mA, MotorCurrentPeak_a1_mA, PanelCurrent_mA and 6 more;
     rejected 1 attribute: StateOfHealth;
     no more attributes left.
    boruta <- plot(boruta, las = 2, cex.axis = 0.5, xlab = "")

    boruta
    Boruta performed 11 iterations in 5.210097 mins.
     11 attributes confirmed important: MainState, MaxError, MotorCurrent_a1_mA, MotorCurrentPeak_a1_mA,
    PanelCurrent_mA and 6 more;
     1 attributes confirmed unimportant: StateOfHealth;
    LS0tDQp0aXRsZTogIlVwdGltZSBhbmFseXNpcyBvZiBhIGNsb3VkLWJhc2VkIHRlbGVtZXRyeSBzeXN0ZW0iDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgZmlnX2NhcHRpb246IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCmVkaXRvcl9vcHRpb25zOg0KICBtYXJrZG93bjoNCiAgICB3cmFwOiA3Mg0KLS0tDQoNCiMjIE9iamVjdGl2ZXM6DQpUaGUgb2JqZWN0aXZlIG9mIHRoaXMgcHJvamVjdCBpcyB0byBpZGVudGlmeSB0aGUgbW9zdCBwcm9ibGVtYXRpYyBkZXZpY2VzIGluIGEgY2xvdWQtYmFzZWQgdGVsZW1ldHJ5IHN5c3RlbS4gPGJyPg0KVGhlIGFuYWx5c2lzIHdpbGwgaGF2ZSB0byBzYXRpc2Z5IHRoZSBmb2xsb3dpbmcgcmVxdWlyZW1lbnRzOiANCg0KPGxpPiBQcm9kdWNlIGFuIGFuYWx5c2lzIGFuZCByZXBvcnQgb2YgZGV2aWNlcyB1cHRpbWUgZm9yIDI0aCBpbiAxNSBtaW4gaW50ZXJ2YWxzIDwvbGk+DQo8bGk+IEZvciBkZXZpY2VzIHdpdGggYW4gdXB0aW1lIGJlbG93IDk3JSwgcmVwb3J0IHRoZSAyIG1vc3QgY29tbW9uIGFsYXJtcy4gPC9saT4NCjxsaT4gVXB0aW1lIGRlZmluaXRpb246DQokJA0KdXB0aW1lID0gMTAwICogKDEgLSBcZnJhY3tpbnQuXCB3aXRoIFwgTkEgKyBpbnQuIFwgd2l0aCBcIGFsYXJtcyAtIGludC5cIHdpdGggXCAgbWFpbnRlbmFuY2UgXCBhbGFybXN9e3RvdGFsIFwgYWN0aXZlIFwgaW50ZXJ2YWxzfSkNCiQkIDwvbGk+DQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgdXBsb2FkIGxpYnJhcmllcw0KcGFja2FnZXMgPC0gYygidGlkeXZlcnNlIiwgImRwbHlyIiwia25pdHIiLCAiZ2dwbG90MiIsICJSU1FMaXRlIiwgIkRCSSIsICJSTXlTUUwiLCAibHVicmlkYXRlIiwgInBsb3RseSIsICJocmJydGhlbWVzIiwgImdndGhlbWVzIiwiQm9ydXRhIiwgInBhdGNod29yayIpDQpsYXBwbHkocGFja2FnZXMsIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCmBgYA0KDQojIyMgU291cmNlIFNRTCBkYXRhDQoNCjxvbD4NCg0KPGxpPkltcG9ydCB0aGUgInRlY2huaWNhbGxfYXNzZXNzbWVudC5zcWwiIGludG8gYSBTUUwgZGF0YWJhc2U8L2xpPg0KDQo8bGk+UHVsbCB0aGUgU1FMIHRhYmxlcyBpbnRvIGludG8gUiBkYXRhZnJhbWVzPC9saT4NCg0KPGxpPk9wZXJhdGUgd2l0aGluIFIgYXMgb24gYSBkYXRhZnJhbWU8L2xpPg0KDQo8L29sPg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBQdWxsaW5nIGRhdGEgZnJvbSBTUUwNCiMgRXN0YWJsaXNoIGEgY29ubmVjdGlvbiB0byBNeVNRTA0KY29uIDwtIGRiQ29ubmVjdChNeVNRTCgpLCB1c2VyID0gInJvb3QiLCBwYXNzd29yZCA9ICIxMjM0IiwgZGJuYW1lID0gInRlbGVtZXRyeV9kYXRhIiwgaG9zdCA9ICJsb2NhbGhvc3QiKQ0KDQojIFF1ZXJ5IHRoZSBkYXRhDQpkZXZpY2VzX2dhdGV3YXlfMSA8LSBkYkdldFF1ZXJ5KGNvbiwgIlNFTEVDVCAqIEZST00gZGV2aWNlc19nYXRld2F5XzEiKQ0KZGV2aWNlc19nYXRld2F5XzIgPC0gZGJHZXRRdWVyeShjb24sICJTRUxFQ1QgKiBGUk9NIGRldmljZXNfZ2F0ZXdheV8yIikNCmRldmljZXNfZ2F0ZXdheV8zIDwtIGRiR2V0UXVlcnkoY29uLCAiU0VMRUNUICogRlJPTSBkZXZpY2VzX2dhdGV3YXlfMyIpDQpnYXRld2F5XzEgPC0gZGJHZXRRdWVyeShjb24sICJTRUxFQ1QgKiBGUk9NIGdhdGV3YXlfMSIpDQpnYXRld2F5XzIgPC0gZGJHZXRRdWVyeShjb24sICJTRUxFQ1QgKiBGUk9NIGdhdGV3YXlfMiIpDQpnYXRld2F5XzMgPC0gZGJHZXRRdWVyeShjb24sICJTRUxFQ1QgKiBGUk9NIGdhdGV3YXlfMyIpDQoNCiMgQ2xvc2UgdGhlIGNvbm5lY3Rpb24NCmRiRGlzY29ubmVjdChjb24pDQpgYGANCg0KVGhlIGRhdGFiYXNlIHRlbGVtZXRyeV9kYXRhIGNvbnRhaW5zIHNpeCB0YWJsZXMuIEkgaW1wb3J0IHRoZW0gYWxsLiBGcm9tDQp0aGUgaW5zdHJ1Y3Rpb25zIGdpdmVuICBJIGFzc3VtZSBvbmx5IGRldmljZXNfZ2F0ZXdheSAoMSwyLDMpIGFyZQ0KcmVsZXZhbnQuIDxicj4NCkRhdGEgaGFzIDEgbWludXRlIGZyZXF1ZW5jeSBieSBkZXZpY2UuIDxicj4gDQo8Yj5PYmplY3RpdmUgMTogPC9iPiBDcmVhdGUgYW4gdXB0aW1lIHZhcmlhYmxlIGFnZ3JlZ2F0ZWQgaW4gMTUgbWludXRlcyBpbnRlcnZhbHMuDQoNCiMjIyBEYXRhIG1hbmlwdWxhdGlvbg0KDQpUd28gYXBwcm9hY2hlczoNCg0KPG9sPg0KDQo8bGk+V29yayBvbiB0aGUgc2luZ2xlIHRhYmxlczogbGlnaHRlciBhbmQgZmFzdGVyIGJ1dCBtb3JlIGNvZGluZw0KZGVtYW5kaW5nPC9saT4NCg0KPGxpPkpvaW4gdGFibGVzIGludG8gYSBzaW5nbGUgZGY6IHNsb3dlciBidXQgbGVzcyBjb2RpbmcgZGVtYW5kaW5nPC9saT4NCg0KPG9sPg0KDQojIyMjIFN0ZXBzOg0KDQo8b2w+DQoNCjxsaT48Yj5DcmVhdGUgYSBnYXRld2F5LWRldmljZSBJRDwvYj48L2xpPg0KDQpEYXRhIGVudGVyIGZybSBkaWZmZXJlbnQgcG9pbnQgb2YgYWNjZXNzLiBTb21lIGRldmljZV9pZCBhcmUgZHVwbGljYXRlZA0KaW4gZGV2aWNlc19nYXRld2F5XzEgYW5kIDIuIDxicj4gVGh1cywgZm9sbG93aW5nIGFwcHJvYWNoIDEsIEkgY3JlYXRlIGENCmdhdGV3YXlfZGV2aWNlX2lkIFw8LSBJRC4gQWxzbyBjaGFuZ2UgZGF0ZSBmb3JtYXQgYW5kIG9yZGVyIGRhdGFzZXRzIGJ5DQpJRCBhbmQgdGltZS4NCg0KYGBge3J9DQojIENyZWF0ZSBhIGRldmljZSBpZGVudGlmaWVyIGJ5IGdhdGV3YXk6IGFzIGRldmljZXMgYXJlIHJlcGVhdGVkIGFjcm9zcyBnYXRld2F5cw0KDQojIENyZWF0ZSBhIGxpc3Qgb2YgZGF0YXNldHMNCmRhdGFzZXRzIDwtIGxpc3QoZGV2aWNlc19nYXRld2F5XzEsIGRldmljZXNfZ2F0ZXdheV8yLCBkZXZpY2VzX2dhdGV3YXlfMykNCg0KIyBMb29wIHRocm91Z2ggZWFjaCBkYXRhc2V0DQpmb3IgKGkgaW4gc2VxX2Fsb25nKGRhdGFzZXRzKSkgew0KICAjIEV4dHJhY3QgZ2F0ZXdheSBudW1iZXINCiAgZ2F0ZXdheV9udW1iZXIgPC0gaQ0KDQogICMgQWRkIElEIGNvbHVtbiBvbiBldmVyeSBkYXRhc2V0DQogIGRhdGFzZXRzW1tpXV0gPC0gbXV0YXRlKGRhdGFzZXRzW1tpXV0sIElEID0gcGFzdGUoZ2F0ZXdheV9udW1iZXIsIGRldmljZV9pZCwgc2VwID0gIl8iKSkgJT4lDQogICAgcmVsb2NhdGUoSUQsIC5hZnRlciA9IHV0Y19kYXRldGltZSkNCg0KICAjIENoYW5nZSBkYXRlIGZvcm1hdA0KICBkYXRhc2V0c1tbaV1dJHV0Y19kYXRldGltZSA8LSBhcy5QT1NJWGN0KGRhdGFzZXRzW1tpXV0kdXRjX2RhdGV0aW1lKQ0KDQogICMgT3JkZXIgZW50cmllcyBieSBkYXRlIGFuZCBJRA0KICBkYXRhc2V0c1tbaV1dIDwtIGRhdGFzZXRzW1tpXV0gJT4lDQogICAgYXJyYW5nZShJRCwgdXRjX2RhdGV0aW1lKQ0KfQ0KYGBgDQoNCk5leHQsIGZvciBlYXNpbmVzcywgSSBqb2luIHRoZSB0aHJlZSB0YWJsZXMgaW50byBhIHNpbmdsZSBkZi4NCg0KYGBge3J9DQojIENvbWJpbmUgdGhlIHByb2Nlc3NlZCBkYXRhc2V0cyBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZSB0byBzaW1wbGlmeSB2aXN1YWxpemF0aW9uIGFuZCBtYW5pcHVsYXRpb24NCmNvbWJpbmVkX2RhdGFzZXQgPC0gZG8uY2FsbChyYmluZCwgZGF0YXNldHMpDQojIFNlbGVjdCByZWxldmFudCBkYXRhc2V0DQpkYXRhIDwtIGNvbWJpbmVkX2RhdGFzZXQNCmBgYA0KDQo8bGk+PGI+Q2hlY2sgdGhlIGRhdGUgc3RydWN0dXJlPC9iPjwvbGk+DQoNClRoZSBqb2luZWQgZGF0YWZyYW1lIGNvbnRhaW5zIDI2NCBkZXZpY2VzLiBFYWNoIGRldmljZSBzaG91bGQgY29sbGVjdA0KZGF0YSB3aXRoIDEgbWludXRlIGZyZXF1ZW5jeS4NCg0KYGBge3J9DQojIG51bWJlciBmIGRldmljZXMNCmxlbmd0aCh1bmlxdWUoZGF0YSRJRCkpDQpgYGANCg0KVGhlcmUgYXJlIGJyZWFrcyBpbiB0aGUgdXRjX2RhdGV0aW1lIHZhcmlhYmxlLiBTb21lIGRldmljZXMgInNraXAiIHNvbWUNCm1pbnV0ZXMuIDxicj4gQXMgbWlzc2luZyBkYXRhIGFyZSByZWxldmFudCBmb3IgdXB0aW1lIGNhbGN1bGF0aW9uLCBJDQppbnRyb2R1Y2UgZW1wdHkgbGluZXMgZm9yIHRoZSBtaXNzaW5nIHBvaW50cyBvZiB0aW1lIDxlbT4gYnkgSUQgZ3JvdXBzDQo8L2VtPi4NCg0KYGBge3J9DQojIFN0ZXAgMTogSWRlbnRpZnkgdGhlIGNvbXBsZXRlIHJhbmdlIG9mIHRpbWVzdGFtcHMgZm9yIGVhY2ggZGV2aWNlX2lkDQojIEl0IGNyZWF0ZXMgYSBsaXN0IG9mIHRpbWVzdGFtcHMgZm9yIHRoZSAyNjcgZGV2aWNlc19nYXRld2F5DQpjb21wbGV0ZV9yYW5nZXMgPC0gYnkoZGF0YSwgZGF0YSRJRCwgZnVuY3Rpb24oeCkgew0KICBtaW5fdGltZSA8LSBtaW4oeCR1dGNfZGF0ZXRpbWUpDQogIG1heF90aW1lIDwtIG1heCh4JHV0Y19kYXRldGltZSkNCiAgc2VxKG1pbl90aW1lLCBtYXhfdGltZSwgYnkgPSAibWluIikNCn0pDQoNCiMgU3RlcCAyOiBDcmVhdGUgYSB0ZW1wbGF0ZSBkYXRhIGZyYW1lIHdpdGggYWxsIHRpbWVzdGFtcHMgd2l0aGluIHRoZSBpZGVudGlmaWVkIHJhbmdlDQp0ZW1wbGF0ZV9kZiA8LSBkby5jYWxsKHJiaW5kLCBsYXBwbHkoY29tcGxldGVfcmFuZ2VzLCBmdW5jdGlvbih4KSB7DQogIGRhdGEuZnJhbWUodXRjX2RhdGV0aW1lID0geCkNCn0pKQ0KDQojIEFkZCBkZXZpY2VfaWQgdG8gdGhlIHRlbXBsYXRlIGRhdGEgZnJhbWUNCnRlbXBsYXRlX2RmJElEIDwtIHJlcChuYW1lcyhjb21wbGV0ZV9yYW5nZXMpLCBzYXBwbHkoY29tcGxldGVfcmFuZ2VzLCBsZW5ndGgpKQ0KDQojIFN0ZXAgMzogTWVyZ2UgdGhlIHRlbXBsYXRlIGRhdGEgZnJhbWUgd2l0aCBvcmlnaW5hbCBkYXRhLCBmaWxsaW5nIG1pc3NpbmcgdmFsdWVzIHdpdGggTkENCm1lcmdlZF9kZiA8LSBtZXJnZSh0ZW1wbGF0ZV9kZiwgZGF0YSwgYnkgPSBjKCJ1dGNfZGF0ZXRpbWUiLCAiSUQiKSwgYWxsLnggPSBUUlVFKQ0KDQojIElmIHlvdSB3YW50IHRvIG9yZGVyIHRoZSBtZXJnZWQgZGF0YSBmcmFtZSBieSBkZXZpY2VfaWQgYW5kIHV0Y19kYXRldGltZQ0KbWVyZ2VkX2RmIDwtIG1lcmdlZF9kZltvcmRlcihtZXJnZWRfZGYkSUQsIG1lcmdlZF9kZiR1dGNfZGF0ZXRpbWUpLCBdDQoNCiMgTm93IHRoZSBtaXNzaW5nIG9ic2VydmF0aW9ucyBJIGFkZGVkIHNob3VsZCBjb250YWluIGFsbCBuYXMgaW4gY29sdW1ucyAzOjE2DQphbGxfbmFfcm93cyA8LSBtZXJnZWRfZGYgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZmlsdGVyKHJvd1N1bXMoaXMubmEoc2VsZWN0KC4sIDM6MTYpKSkgPT0gMTQpDQoNCmRhdGEgPC0gbWVyZ2VkX2RmDQpgYGANCg0KSSBkaXNwbGF5IHRoZSBpbnRyb2R1Y2VkIG1pc3NpbmcgcG9pbnRzOg0KDQpgYGB7cn0NCmFsbF9uYV9yb3dzDQpgYGANCg0KTmV4dCwgSSBzdGlsbCBuZWVkIGEgZGF5IGFuZCBpbnRlcnZhbCBpZGVudGlmaWVyIDxicj4gSSBpbnRyb2R1Y2UgYSBJRCBmb3IgdGhlIGRheQ0KbnVtYmVyIHNpbmNlIHRoZSBzdGFydCBvZiB0aGUgYW5hbHlzaXM6IDIzLTEwLjxicj4gDQpCZWxvdyBpcyBkaXNwbGF5ZWQgdGhlIGxvb2sgb2YgdGhlIGRhdGFzZXQgYWZ0ZXIgdGhpcyBkYXRhIG1hbmlwdWxhdGlvbi4gDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSB2YXJpYWJsZSBmb3IgdGhlIGRheSBudW1iZXINCmRhdGEgPC0gZGF0YSAlPiUNCiAgbXV0YXRlKGRheSA9IGFzLmludGVnZXIoZGF0ZSh1dGNfZGF0ZXRpbWUpIC0gbWluKGRhdGUodXRjX2RhdGV0aW1lKSkpICsgMSkgJT4lDQogIHJlbG9jYXRlKGRheSwgLmFmdGVyID0gdXRjX2RhdGV0aW1lKQ0KDQojIENyZWF0ZSBhIHZhcmlhYmxlIGZvciB0aGUgaW50ZXJ2YWwgbnVtYmVyIHdpdGhpbiBlYWNoIGRheQ0KZGF0YSRpbnRlcnZhbCA8LSAxICsgKChob3VyKGRhdGEkdXRjX2RhdGV0aW1lKSAqIDYwICsgbWludXRlKGRhdGEkdXRjX2RhdGV0aW1lKSkgJS8lIDE1KQ0KZGF0YSA8LSBkYXRhICU+JQ0KICByZWxvY2F0ZShpbnRlcnZhbCwgLmFmdGVyID0gdXRjX2RhdGV0aW1lKQ0KDQpwcmludChkYXRhKQ0KYGBgDQpEYXRhc2V0IGRlc2NyaXB0aXZlczoNCmBgYHtyLCBmaWcud2lkdGg9MTB9DQpkYXRhLmZyYW1lKA0KICBWYXJpYWJsZSA9IGMoInV0Y19kYXRldGltZSIsImludGVydmFsIiwiZGF5IiwiSUQiLCJkZXZpY2VfaWQiLCJBbGFybXMxIiwiQWxhcm1zMiIsIlBhbmVsVm9sdGFnZV9tViIgLCAiUG9zaXRpb25fYTFfcmFkIiAsICJNb3RvckN1cnJlbnRfYTFfbUEiICwiTW90b3JDdXJyZW50UGVha19hMV9tQSIsICJUYXJnZXRBbmdsZV9hMV9yYWQiLCAiUGFuZWxDdXJyZW50X21BIiAsICAgICJNYXhFcnJvciIsIlN0YXRlT2ZDaGFyZ2UiICAsICAgICJWb2x0YWdlX21WIiAgICwgICJTdGF0ZU9mSGVhbHRoIiwgICAgICAiTWFpblN0YXRlIiwgICAgICAiU2FmZVBvc2l0aW9uU3RhdGUiKSwNCiAgTnJfT2JzID0gYygyNjc2ODIzLDI2NzY4MjMsMjY3NjgyMywyNjc2ODIzLDI2NzY4MjMsMjY3NjgyMywyNjc2ODIzLDI2NzY4MjMsMjY3NjgyMywyNjc2ODIzLDI2NzY4MjMsMjY3NjgyMywyNjc2ODIzLDI2NzY4MjMsMjY3NjgyMywyNjc2ODIzLDI2NzY4MjMsMjY3NjgyMywyNjc2ODIzKSwNCiAgTWluID0gYygiMjAyMy0xMC0yMyAwMDowMDowMC4wMCIsMSwxLCIxXzEiLDEsMCwwLDAsLTAuOSwwLDAsLTAuOSwwLDAsMCwwLDAsMCwwKSwNCiAgTWF4ID0gYygiMjAyMy0xMC0yOSAyMzo1OTowMC4wMCIsOTYsNywiM18xMjciLDEzNywxNjM4NCwyNjAsNDkwMjUsMC45LDI3NjEsMzM5MSwwLjksODQ5LDI1NSwxMDAsNDkwMjUsMCwyLDEpDQopDQpgYGANCg0KDQo8bGk+PGI+VXB0aW1lIHZhcmlhYmxlIGNhbGN1bGF0aW9uPC9iPjwvbGk+DQoNCiQkDQp1cHRpbWUgPSAxMDAgKiAoMSAtIFxmcmFje2ludC5cIHdpdGggXCBOQSArIGludC4gXCB3aXRoIFwgYWxhcm1zIC0gaW50Llwgd2l0aCBcICBtYWludGVuYW5jZSBcIGFsYXJtc317dG90YWwgXCBhY3RpdmUgXCBpbnRlcnZhbHN9KQ0KJCQNCkFjY29yZGluZyB0byB0aGUgaW5mbyByZWNlaXZlZCwgdXB0aW1lIGNhbiBiZSBjYWxjdWxhdGVkIGluIG11bHRpcGxlIHdheXMuIDxicj4NCkFzIGl0IGlzIGNhbGN1bGF0ZWQgb24gPHU+MTUgbWludXRlcyBpbnRlcnZhbHM8L3U+IGFuZCBhbGFybXMgaGFwcGVuIGF0IHRoZQ0KPHU+bWludXRlLWxldmVsPC91Piwgd2l0aGluIGFuIGludGVydmFsIG9mIDE1IG1pbnV0ZXMgPGI+dGhlcmUgY2FuIGJlIGRpZmZlcmVudA0KY29tYmluYXRpb25zIG9mIGFsYXJtczwvYj4uPGJyPiBGb3IgaW5zdGFuY2UsIHdlIG1heSBzZXQgYWxhcm0gPSAxIGlmIHRoZXJlDQp3YXMgYXQgbGVhc3QgMSBhbGFybSB3aXRoaW4gdGhlIDE1IG1pbnV0ZXMgaW50ZXJ2YWwuIEhvd2V2ZXIsIHRoaXMgaGFzDQpzdHJvbmcgaW1wbGljYXRpb25zIGZvciB1cHRpbWUgY2FsY3VsYXRpb25zIGFuZCBhbG1vc3Qgbm8gZGV2aWNlIHdvdWxkDQpoYXZlIGEgdW5wdGltZSBcPDk3JSAoSSBwcm92aWRlIHRoZSBjb2RlIGJlbG93IGZvciB0aGlzLCBidXQgZG8gbm90IHVzZQ0KdGhpcyBkZWZpbml0aW9uKS4gPGJyPiBBbiBhbHRlcm5hdGl2ZSwgaXMgdG8gc2V0IGFsYXJtcyA9IDEgaWYgYSBtaW5pbXVtDQpudW1iZXIgb2YgYWxhcm1zIGhhcHBlbnMgd2l0aGluIGVhY2ggaW50ZXJ2YWwuIEkgdXNlIGEgdG90YWwgb2YgMTUNCmFsYXJtcyBwZXIgZGV2aWNlLWludGVydmFsIChpZiBBbGFybXMxK0FsYXJtczIgXD49IDE1LS1cPiBBbGFybXMgPSAxKSBhcw0KaXQgaXMgYXBwcm94aW1hdGVseSB0aGUgaGFsZiBhbGFybXMgbnVtYmVyIHdlIGNhbiBoYXZlIG9uIGFuIGludGVydmFsLg0KPGI+VGhpcyBpcyBkaXNjcmV0aW9uYXJ5IGFuZCBjYW4gYmUgbW9kaWZpZWQuPC9iPg0KDQpgYGB7cn0NCiMgQWdncmVnYXRlIGJ5IElEIGFuZCBpbnRlcnZhbA0KIyAyIG9wdGlvbnMgZm9yIHRoZSBjYWxjdWxhdGlvbiBvZiBhbGFybXM6IDEpIGlmIGFueSBhbGFybS0tPlRSVUU7IDIpIGlmIG1vcmUgdGhhbiBoYWxmIGFsYXJtcy0tPiBUUlVFDQoNCiMgb3B0aW9uIDEpDQphZ2dyZWdhdGVkX2RhdGFfTk8gPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoSUQsIGRheSwgaW50ZXJ2YWwpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgYWxhcm1zID0gYW55KEFsYXJtczEgIT0gMCB8IEFsYXJtczIgIT0gMCxuYS5ybT1UKSwNCiAgICBtYWludGVuYW5jZV9hbGFybSA9IGFueShBbGFybXMxID09IDE2LG5hLnJtPVQpLA0KICAgIG1pc3NpbmcgPSBhbGwoaXMubmEoUGFuZWxWb2x0YWdlX21WKSAmIGlzLm5hKFBvc2l0aW9uX2ExX3JhZCkgJiBpcy5uYShNb3RvckN1cnJlbnRfYTFfbUEpICYgaXMubmEoTW90b3JDdXJyZW50UGVha19hMV9tQSkgJiBpcy5uYShUYXJnZXRBbmdsZV9hMV9yYWQpICYgaXMubmEoUGFuZWxDdXJyZW50X21BKSAmIGlzLm5hKE1heEVycm9yKSAmIGlzLm5hKFN0YXRlT2ZDaGFyZ2UpICYgaXMubmEoVm9sdGFnZV9tVikgJiBpcy5uYShTdGF0ZU9mSGVhbHRoKSAmIGlzLm5hKE1haW5TdGF0ZSkgJiBpcy5uYShTYWZlUG9zaXRpb25TdGF0ZSkpDQogICkNCiMgb3B0aW9uIDIpOg0KYWdncmVnYXRlZF9kYXRhIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KElELCBkYXksIGludGVydmFsKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGFsYXJtcyA9IHN1bShBbGFybXMxICE9IDAgfCBBbGFybXMyICE9IDAsIG5hLnJtPVQpID49IDE1LCAjY291bnRzIGluc3RhbmNlcyBvZiBhbGFybSAxIG9yIDIgZGlmZmVyZW50IGZyb20gemVybywgaWYgPj0gMTUgVFJVRSwgby53IEZBTFNFDQogICAgbWFpbnRlbmFuY2VfYWxhcm0gPSBhbnkoQWxhcm1zMSA9PSAxNiwgbmEucm0gPSBUKSwgIyBpZiBhbnkgYWxhcm0xPTE2IFRSVUUsIG8udyBGQUxTRQ0KICAgIG1pc3NpbmcgPSBhbGwoaXMubmEoUGFuZWxWb2x0YWdlX21WKSAmIGlzLm5hKFBvc2l0aW9uX2ExX3JhZCkgJiBpcy5uYShNb3RvckN1cnJlbnRfYTFfbUEpICYgaXMubmEoTW90b3JDdXJyZW50UGVha19hMV9tQSkgJiBpcy5uYShUYXJnZXRBbmdsZV9hMV9yYWQpICYgaXMubmEoUGFuZWxDdXJyZW50X21BKSAmIGlzLm5hKE1heEVycm9yKSAmIGlzLm5hKFN0YXRlT2ZDaGFyZ2UpICYgaXMubmEoVm9sdGFnZV9tVikgJiBpcy5uYShTdGF0ZU9mSGVhbHRoKSAmIGlzLm5hKE1haW5TdGF0ZSkgJiBpcy5uYShTYWZlUG9zaXRpb25TdGF0ZSkpI2lmIGFsbCB2YXJzIGlzLm5hIFRSVUUsIEZBTFNFIG8udy4NCiAgKQ0KcHJpbnQoYWdncmVnYXRlZF9kYXRhKQ0KYGBgDQoNCkRhdGFmcmFtZSBmb3IgdXB0aW1lIGNhbGN1bGF0aW9uOg0KYGBge3J9DQojb3B0aW9uIDEpIA0KdXB0aW1lX2NvbXBvbmVudHNfTk8gPC0gYWdncmVnYXRlZF9kYXRhX05PICU+JQ0KICBncm91cF9ieShJRCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICB0b3RhbF9pbnRlcnZhbHMgPSBuKCksDQogICAgbWlzc2luZ19pbnRlcnZhbHMgPSBzdW0obWlzc2luZywgbmEucm0gPSBUKSwNCiAgICBhbGFybV9pbnRlcnZhbHMgPSBzdW0oYWxhcm1zLCBuYS5ybSA9IFQpLA0KICAgIG1haW50ZW5hbmNlX2FsYXJtX2ludGVydmFscyA9IHN1bShtYWludGVuYW5jZV9hbGFybSwgbmEucm0gPSBUKQ0KICApICU+JQ0KICBtdXRhdGUoDQogICAgdXB0aW1lID0gMTAwICogKDEgLSAoKG1pc3NpbmdfaW50ZXJ2YWxzICsgYWxhcm1faW50ZXJ2YWxzIC0gbWFpbnRlbmFuY2VfYWxhcm1faW50ZXJ2YWxzKSAvIHRvdGFsX2ludGVydmFscykpDQogICkNCiNvcHRpb24gMikNCnVwdGltZV9jb21wb25lbnRzIDwtIGFnZ3JlZ2F0ZWRfZGF0YSAlPiUNCiAgZ3JvdXBfYnkoSUQpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgdG90YWxfaW50ZXJ2YWxzID0gbigpLA0KICAgIG1pc3NpbmdfaW50ZXJ2YWxzID0gc3VtKG1pc3NpbmcsIG5hLnJtID0gVCksDQogICAgYWxhcm1faW50ZXJ2YWxzID0gc3VtKGFsYXJtcywgbmEucm0gPSBUKSwNCiAgICBtYWludGVuYW5jZV9hbGFybV9pbnRlcnZhbHMgPSBzdW0obWFpbnRlbmFuY2VfYWxhcm0sIG5hLnJtID0gVCkNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgIGRvd250aW1lID0gMTAwICogKChtaXNzaW5nX2ludGVydmFscyArIGFsYXJtX2ludGVydmFscyAtIG1haW50ZW5hbmNlX2FsYXJtX2ludGVydmFscykgLyB0b3RhbF9pbnRlcnZhbHMpDQogICkgJT4lDQogIG11dGF0ZSh1cHRpbWUgPSAxMDAgLSBkb3dudGltZSkNCg0KdXB0aW1lX2NvbXBvbmVudHMNCmBgYA0KDQpIZXJlIEkgc2hvdyBob3cgZGlmZmVyZW50IGRlZmluaXRpb25zIG9mIHVwdGltZSBjYW4gbGVhZCB0byBkaWZmZXJlbnQNCnNpdHVhdGlvbnMuIDxicj4NCg0KYGBge3IsIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsIG91dC53aWR0aCA9ICIxMi41aW4ifQ0KIyBDcmVhdGUgYSBiYXJwbG90IG9yIGhpc3RvZ3JhbQ0KcDAgPC0gZ2dwbG90KHVwdGltZV9jb21wb25lbnRzX05PLCBhZXMoeCA9IGZhY3Rvcih1cHRpbWUgPCA5NyksIGZpbGwgPSBmYWN0b3IodXB0aW1lIDwgOTcpKSkgKw0KICBnZW9tX2JhcigpICsNCiAgZ2VvbV90ZXh0KHN0YXQgPSAiY291bnQiLCBhZXMobGFiZWwgPSAuLmNvdW50Li4pLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKSArICMgQWRkIGxhYmVscyBvbiB0b3Agb2YgYmFycw0KICB4bGFiKCJVcHRpbWUgPCA5NyUiKSArICMgU2V0IHgtYXhpcyBsYWJlbA0KICB5bGFiKCJDb3VudCIpICsgIyBTZXQgeS1heGlzIGxhYmVsDQogIHRoZW1lX2NsZWFuKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDE2KSwgIyBDZW50ZXIgYW5kIGluY3JlYXNlIHRpdGxlIHNpemUNCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKQ0KICApICsgIyBBZGp1c3Qgc2l6ZSBvZiB4LWF4aXMgbGFiZWwNCiAgZ2d0aXRsZSgiVXB0aW1lIGRpc3RyaWJ1dGlvbiAtIERlZmluaXRpb24gMSAoZGlzcmVnYXJkZWQpIikNCg0KcDEgPC0gZ2dwbG90KHVwdGltZV9jb21wb25lbnRzLCBhZXMoeCA9IGZhY3Rvcih1cHRpbWUgPCA5NyksIGZpbGwgPSBmYWN0b3IodXB0aW1lIDwgOTcpKSkgKw0KICBnZW9tX2JhcigpICsNCiAgZ2VvbV90ZXh0KHN0YXQgPSAiY291bnQiLCBhZXMobGFiZWwgPSAuLmNvdW50Li4pLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKSArICMgQWRkIGxhYmVscyBvbiB0b3Agb2YgYmFycw0KICB4bGFiKCJVcHRpbWUgPCA5NyUiKSArICMgU2V0IHgtYXhpcyBsYWJlbA0KICB5bGFiKCJDb3VudCIpICsgIyBTZXQgeS1heGlzIGxhYmVsDQogIHRoZW1lX2NsZWFuKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDE2KSwgIyBDZW50ZXIgYW5kIGluY3JlYXNlIHRpdGxlIHNpemUNCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKQ0KICApICsgIyBBZGp1c3Qgc2l6ZSBvZiB4LWF4aXMgbGFiZWwNCiAgZ2d0aXRsZSgiVXB0aW1lIGRpc3RyaWJ1dGlvbiAtIERlZmluaXRpb24gMiAoYWRvcHRlZCkiKQ0KDQpwMCArIHAxDQpgYGANCg0KQWRvcHRpbmcgdGhlIHNlY29uZCBkZWZpbml0aW9uLCBhY3Jvc3MgdGhlIDMgZ2F0ZXdheXMsIHRoZXJlIGFyZSAyNjQNCmRldmljZXMgZGl2aWRlZCBieSB1cHRpbWUgdmFsdWVzIGFib3ZlIG9yIGJlbG93IDk3Ljxicj4gQmVsb3cgSSBzaG93DQp1cHRpbWUgZGlzdHJpYnV0aW9uLiA8YnI+IEFzaWRlIGZyb20gYW4gb3V0bGllciAoZGV2aWNlIDNfMTIxKSwgdXB0aW1lDQppcyBjb25jZW50cmF0ZWQgYmV0d2VlbiA4MCUgYW5kIDk5JSwgd2l0aCBtZWRpYW4gY2xvcyB0byA5OSUuDQoNCmBgYHtyfQ0KIyBQbG90IHVwdGltZSBkaXN0cmlidXRpb24NCmdncGxvdChkYXRhID0gdXB0aW1lX2NvbXBvbmVudHMsIGFlcyh4ID0gIiIsIHkgPSB1cHRpbWUpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gInNreWJsdWUiLCBjb2xvciA9ICJibGFjayIpICsgIyBDdXN0b21pemUgYm94cGxvdCBhcHBlYXJhbmNlDQogIHhsYWIoIlVwdGltZSIpICsgIyBSZW1vdmUgeC1heGlzIGxhYmVsDQogIHlsYWIoIiglKSIpICsgIyBTZXQgeS1heGlzIGxhYmVsDQogIHRoZW1lX2NsZWFuKCkgKyAjIEFwcGx5IG1pbmltYWwgdGhlbWUNCiAgdGhlbWUoDQogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgIyBSZW1vdmUgbWFqb3IgZ3JpZGxpbmVzDQogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKQ0KICApICsgIyBSZW1vdmUgbWlub3IgZ3JpZGxpbmVzDQogIGdndGl0bGUoIlVwdGltZSBEaXN0cmlidXRpb24gKG49MjY0IGRldmljZXMpIikgIyBBZGQgdGl0bGUNCmBgYA0KDQpgYGB7cn0NCnRhYmxlIDwtIGRhdGEuZnJhbWUoDQogIFVwdGltZSA9IGMoIjw5NyUiLCAiPj0gOTclIiksDQogIERldmljZXNfbnVtYmVyID0gYyhzdW0odXB0aW1lX2NvbXBvbmVudHMkdXB0aW1lIDwgOTcpLCBzdW0odXB0aW1lX2NvbXBvbmVudHMkdXB0aW1lID49IDk3KSkNCikNCg0KdG90YWwgPC0gYygNCiAgIlRvdGFsIiwNCiAgc3VtKGFzLm51bWVyaWModGFibGUkRGV2aWNlc19udW1iZXIpKQ0KKQ0KdGFibGUgPC0gcmJpbmQodGFibGUsIHRvdGFsKQ0KYGBgDQoNCjxsaT48Yj5JZGVudGlmeSBkZXZpY2VzIHdpdGggdXB0aW1lIFw8IDk3JTwvYj48L2xpPg0KDQpgYGB7cn0NCnRhYmxlDQpgYGANCg0KYGBge3J9DQojIFNlbGVjdCBkZXZpY2VzIHdpdGggdXB0aW1lPDk3JQ0KZGV2aWNlc19iYWQgPC0gdXB0aW1lX2NvbXBvbmVudHMgJT4lDQogIGZpbHRlcih1cHRpbWUgPCA5NykgJT4lDQogIHB1bGwoSUQpDQoNCg0KIyBGaWx0ZXIgZGF0YSBiYXNlZCBvbiBiYWQgSURzDQpkZXZpY2VzX2JhZCA8LSBkYXRhICU+JQ0KICBmaWx0ZXIoSUQgJWluJSBkZXZpY2VzX2JhZCkgJT4lDQogIGZpbHRlcighaXMubmEoSUQpKQ0KYGBgDQoNCjxsaT48Yj5Gb3IgZGV2aWNlcyB3aXRoIHVwdGltZSBcPCA5NyUsIGlkZW50aWZ5IG1vc3QgZnJlcXVlbnQNCmFsYXJtczwvYj48L2xpPg0KDQpXZSBoYXZlIDIgYWxhcm1zIHZhcmlhYmxlcywgSSBpZGVudGlmeSB0aGUgdHdvIG1vc3QgZnJlcXVlbnQgYWxhcm1zIG9uDQplYWNoIG9mIHRoZSB2YXJpYWJsZXMgYnkgYWdncmVnYXRpbmcgdGhlIGRhdGFzZXQgd2l0aCB1cHRpbWVcPDk3JQ0KZGV2aWNlcyBieSB0d28gbmV3IHZhcmlhYmxlcy48YnI+IFRoZXkgcmVwcmVzZW50IHRoZSBtb3N0IDIgZnJlcXVlbnQNCmFsYXJtcyBmb3IgQWxhcm1zMSBhbmQgQWxhcm1zMiBhbmQgSSBjYWxsIHRoZW0gdG9wX2FsMSwgdG9wX2FsMi4gPGJyPg0KPGI+IElNUE9SVEFOVDo8L2I+IEkgZGlzcmVnYXJkIEFsYXJtc18xIG9yIEFsYXJtc18yID0gMCAoYXMgdGhleSBkbyBub3QNCmNvbnN0aXR1dGUgYWxhcm0gY29kZSkuPGJyPiBZb3UgY2FuIHNlZSB0aGF0IGZvciBBbGFybXNfMiBpbiBtb3N0IGNhc2VzDQp0aGUgbW9zdCBmcmVxdWVudCBhbmQgdW5pcXVlIGFsYXJtIGNvZGUgaXMgQWxhcm1zXzIgPSAwLiBJbiB0aG9zZSBjYXNlcw0KdG9wX2FsMiBpcyBOQS4NCg0KYGBge3J9DQojIEdyb3VwIGJ5IElEIGFuZCBzdW1tYXJpemUgdGhlIHR3byBtb3N0IGZyZXF1ZW50IG5vbi16ZXJvIHZhbHVlcyBmb3IgQWxhcm1zMSBhbmQgQWxhcm1zMg0KYWdncmVnYXRlZF9kYXRhIDwtIGRldmljZXNfYmFkICU+JQ0KICBncm91cF9ieShJRCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICB0b3BfYWwxID0gbmFtZXMoc29ydCh0YWJsZShBbGFybXMxW0FsYXJtczEgIT0gMF0pLCBkZWNyZWFzaW5nID0gVFJVRSlbMToyXSksDQogICAgdG9wX2FsMiA9IHsNCiAgICAgIG5vbl96ZXJvX2FsYXJtczIgPC0gQWxhcm1zMltBbGFybXMyICE9IDBdDQogICAgICBpZiAobGVuZ3RoKHVuaXF1ZShub25femVyb19hbGFybXMyKSkgPj0gMikgew0KICAgICAgICBuYW1lcyhzb3J0KHRhYmxlKG5vbl96ZXJvX2FsYXJtczIpLCBkZWNyZWFzaW5nID0gVFJVRSlbMToyXSkNCiAgICAgIH0gZWxzZSB7DQogICAgICAgIE5BDQogICAgICB9DQogICAgfQ0KICApDQphZ2dyZWdhdGVkX2RhdGENCmBgYA0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIHRoZSBmcmVxdWVuY3kgb2YgZWFjaCBhbGFybSB0eXBlIGJ5IGRldmljZSBJRA0KYWxhcm1fZnJlcXVlbmN5XzEgPC0gZGV2aWNlc19iYWQgJT4lDQogIGdyb3VwX2J5KElELCBBbGFybXMxKSAlPiUNCiAgY291bnQobmFtZSA9ICJBbGFybXMxX2ZyZXF1ZW5jeSIpICU+JQ0KICBmaWx0ZXIoIUFsYXJtczEgPT0gMCkNCiMgUmVzdHJpY3QgdG8gdGhlIG1vc3QgZnJlcXVlbnQgdHdvIGFsYXJtcw0KdG9wX2FsYXJtczEgPC0gYWxhcm1fZnJlcXVlbmN5XzEgJT4lDQogIGdyb3VwX2J5KElELCBBbGFybXMxKSAlPiUNCiAgc3VtbWFyaXNlKHRvdGFsX2ZyZXF1ZW5jeSA9IHN1bShBbGFybXMxX2ZyZXF1ZW5jeSkpICU+JQ0KICBhcnJhbmdlKGRlc2ModG90YWxfZnJlcXVlbmN5KSkgJT4lDQogIHNsaWNlKDE6MikNCg0KIyBDYWxjdWxhdGUgdGhlIGZyZXF1ZW5jeSBvZiBlYWNoIGFsYXJtIHR5cGUgYnkgZGV2aWNlIElEDQphbGFybV9mcmVxdWVuY3lfMiA8LSBkZXZpY2VzX2JhZCAlPiUNCiAgZ3JvdXBfYnkoSUQsIEFsYXJtczIpICU+JQ0KICBjb3VudChuYW1lID0gIkFsYXJtczJfZnJlcXVlbmN5IikgJT4lDQogIGZpbHRlcighQWxhcm1zMiA9PSAwKQ0KDQojIFJlc3RyaWN0IHRvIHRoZSBtb3N0IGZyZXF1ZW50IHR3byBhbGFybXMNCnRvcF9hbGFybXMyIDwtIGFsYXJtX2ZyZXF1ZW5jeV8yICU+JQ0KICBncm91cF9ieShJRCwgQWxhcm1zMikgJT4lDQogIHN1bW1hcmlzZSh0b3RhbF9mcmVxdWVuY3kgPSBzdW0oQWxhcm1zMl9mcmVxdWVuY3kpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHRvdGFsX2ZyZXF1ZW5jeSkpICU+JQ0KICBzbGljZSgxOjIpDQoNCiMgQWRqdXN0IHNpbmdsZSBpbnN0YW5jZXMuIDNfMTIxIGlzIGEgcGFydGljdWxhciBjYXNlIGJlY2F1c2UgaXMgdGhlIG9ubHkgY2FzZSBpdCBoYXMgMiB0b3AgYWxhcm1zIGZyb20gQWxhcm1zMg0KIyBSZW1vdmUgb25lIG9ic2VydmF0aW9uIGZyb20gdG9wX2FsYXJtczENCnRvcF9hbGFybXMxIDwtIHRvcF9hbGFybXMxICU+JQ0KICBmaWx0ZXIoIShBbGFybXMxID09IDEyOCAmIElEID09ICIzXzEyMSIpKQ0KDQojIFJlbW92ZSBvbmUgb2JzZXJ2YXRpb24gZnJvbSB0b3BfYWxhcm1zMg0KdG9wX2FsYXJtczIgPC0gdG9wX2FsYXJtczIgJT4lDQogIGZpbHRlcighKEFsYXJtczIgPT0gNCAmIElEID09ICIzXzEyMSIpKQ0KYGBgDQoNCmBgYHtyfQ0KdG9wX2FsYXJtczENCmBgYA0KDQpgYGB7cn0NCnRvcF9hbGFybXMyDQpgYGANCg0KTmV4dCwgam9pbiB0b3BfYWxhcm1zMSBhbmQgMiBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZS4gQ3JlYXRlIGEgY29sdW1uDQpmb3IgdG9wIGFsYXJtIHR5cGUgYW5kIGZvciBpdHMgIm9yZGVyIi4gPGJyPiBPcmRlciBtZWFucyB3aGV0aGVyIHRoZQ0KYWxhcm0gaXMgdGhlIG1vc3QgZnJlcXVlbnQgb3IgdGhlIHNlY29uZCBtb3N0IGZyZXF1ZW50LCBieSBkZXZpY2UuDQoNCjwvb2w+DQoNCmBgYHtyfQ0KbWVyZ2VkIDwtIG1lcmdlKHRvcF9hbGFybXMxLCB0b3BfYWxhcm1zMiwgYnkgPSAiSUQiLCBhbGwueCA9IFQpDQojIGlkZW50aWZ5IHRvcCBhbGFybXMNCm1lcmdlZCA8LSBtZXJnZWQgJT4lDQogIG11dGF0ZSh0b3BfYWxhcm1zID0gaWZlbHNlKGlzLm5hKEFsYXJtczIpLCBBbGFybXMxLA0KICAgIGlmZWxzZSh0b3RhbF9mcmVxdWVuY3kueCA+IHRvdGFsX2ZyZXF1ZW5jeS55LCBBbGFybXMxLCBBbGFybXMyKQ0KICApKQ0KDQptZXJnZWQgPC0gbWVyZ2VkICU+JQ0KICBncm91cF9ieShJRCkgJT4lDQogIG11dGF0ZShvcmRlciA9IHJvd19udW1iZXIoKSkgJT4lDQogIGFycmFuZ2UoSUQsIG9yZGVyKQ0KDQptZXJnZWRfdGFiIDwtIG1lcmdlZA0KY29sbmFtZXMobWVyZ2VkX3RhYikgPC0gYygiSUQiLCAiQWxhcm1zMSAodHlwZSkiLCAiRnJlcXVlbmN5IEFsYXJtMSAoY291bnQpIiwgIkFsYXJtczIgKHR5cGUpIiwgIkZyZXF1ZW5jeSBBbGFybXMgMiAoY291bnQpIiwgIk1vc3QgZnJlcXVlbnQgYWxhcm0gKHR5cGUpIiwgIlRvcCAxIG9yIDIiKQ0KIyBWaWV3IHRoZSByZXN1bHQNCm1lcmdlZF90YWINCmBgYA0KDQojIyMgUmVzdWx0cyB2aXN1YWxpemF0aW9uDQoNClNvbWUgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyA8YnI+IDkyJSBvZiBtb3N0IGZyZXF1ZW50IGFsYXJtcyBhcmUgY29kZXM6DQo4MTkyOyA0MDk2LiA8YnI+IDxiPiBDQVJFRlVMOiA8L2I+IFRoZSBudW1iZXIgb2YgZGV2aWNlcyB3aXRoIHVwdGltZQ0KXDw5NyUgaXMgMTI3LiBUaHVzLCB0aGUgdG90YWwgYWJzb2x1dGUgZnJlcXVlbmN5IHNob3VsZCBiZSAoMTI3XCoyKT0yNTQNCih0aW1lcyAyIGJlY2F1c2Ugd2UgYXJlIGxvb2tpbmcgYXQgdGhlIDIgbW9zdCBjb21tb24gZXJyb3JzKS48YnI+IFRoZQ0Kc2xpZ2h0IGRpc2NyZXBhbmN5IHdpdGggdGhlIHRvdGFsIHZhbHVlIGluIHRoZSB0YWJsZSBpcyBnaXZlbiBieSB0aGUNCmZhY3QgdGhhdCBub3QgYWxsIGRldmljZXMgaGF2ZSBhIHNlY29uZCBtb3N0IGZyZXF1ZW50IGFsYXJtIChlLmcuIHNvbWUNCmRldmljZXMgbWF5IHNob3cgb25seSBjb2RlcyAwIGFuZCBhbm90aGVyIGJldHdlZW4gODE5MiBvciA0MDk2KS4NCg0KYGBge3J9DQojIENhbGN1bGF0ZSBhYnNvbHV0ZSBmcmVxdWVuY3kgb2YgZWFjaCBhbGFybQ0KYWJzb2x1dGVfZnJlcXVlbmN5IDwtIHRhYmxlKG1lcmdlZCR0b3BfYWxhcm1zKSAjIHRvdGFsIGZyZXF1ZW5jeQ0KDQojIENhbGN1bGF0ZSBwZXJjZW50YWdlcyBvZiBlYWNoIHRvcF9hbGFybXMgZmFjdG9yDQpwZXJjZW50YWdlcyA8LSBhYnNvbHV0ZV9mcmVxdWVuY3kgLyBucm93KG1lcmdlZCkgKiAxMDAgIyB0b3RhbCBmcmVxdWVuY3kgcGVyY2VudGFnZXMNCg0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggcGVyY2VudGFnZXMgYW5kIGFic29sdXRlIGZyZXF1ZW5jaWVzDQpyZXN1bHQgPC0gZGF0YS5mcmFtZSgNCiAgVG9wX2FsYXJtcyA9IGFzLmludGVnZXIobmFtZXMocGVyY2VudGFnZXMpKSwNCiAgQWJzb2x1dGVfZnJlcXVlbmN5ID0gYXMuaW50ZWdlcihhYnNvbHV0ZV9mcmVxdWVuY3kpLA0KICBQZXJjZW50YWdlID0gcm91bmQoYXMubnVtZXJpYyhwZXJjZW50YWdlcyksIDIpDQopDQoNCiMgQWRkIGEgdG90YWwgbGluZQ0KdG90YWwgPC0gYygNCiAgIlRvdGFsIiwNCiAgc3VtKGFzLm51bWVyaWMocmVzdWx0JEFic29sdXRlX2ZyZXF1ZW5jeSkpLA0KICByb3VuZChzdW0oYXMubnVtZXJpYyhyZXN1bHQkUGVyY2VudGFnZSkpLCAwKQ0KKQ0KcmVzdWx0IDwtIHJiaW5kKHJlc3VsdCwgdG90YWwpDQoNCnJlc3VsdA0KYGBgDQoNCiMjIyBJZGVudGlmeWluZyBtb3N0IHByb2JsZW1hdGljIGRldmljZXMNCg0KTmV4dCBwbG90cyBzaG93Ojxicj4gMSkgRGV2aWNlcyB3aXRoIHRoZSBoaWdoZXN0IG51bWJlciBvZiBhbGFybXMgYW1vbmcNCnRob3NlIHdpdGggdXB0aW1lXDw5NzsgPGJyPiAyKSBEZXZpY2VzIHdpdGggdGhlIGhpZ2hlc3QgZG93bnRpbWU7PGJyPiBBcw0KZG93bnRpbWUgaXMgbm90IGp1c3QgZXF1YWwgdG8gdGhlIG51bWJlciBvZiBhbGFybXMgaXQgaXMgcmVhc29uYWJsZSB0bw0KaGF2ZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBkZXZpY2VzIGlkZW50aWZpZWQgaW4gdGhlIHR3byBwbG90cw0KDQpgYGB7ciwgZmlnLmFsaWduID0gImNlbnRlciIsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMiwgb3V0LndpZHRoID0gIjEyLjVpbiJ9DQoNCmZyZXEgPC0gbWVyZ2UoYWxhcm1fZnJlcXVlbmN5XzEsIGFsYXJtX2ZyZXF1ZW5jeV8yLCBieSA9ICJJRCIsIGFsbC54ID0gVCkgIyBtZXJnZSBmcmVxdWVuY2llcyBvZiBhbGwgYWxhcm1zIGluIGRldmljZXMgd2l0aCB1cHRpbWUgPDk3JQ0KZnJlcSA8LSBmcmVxICU+JQ0KICBncm91cF9ieShJRCkgJT4lDQogIG11dGF0ZSh0b3RhbF9mcmVxMV8yID0gc3VtKEFsYXJtczFfZnJlcXVlbmN5LCBBbGFybXMyX2ZyZXF1ZW5jeSwgbmEucm0gPSBUKSkgJT4lICMgY3JlYXRlIGEgdmFyaWFibGUgZm9yIHRoZSBzdW0gb2YgYWxsIGFsYXJtcyggdG90YWwgYWxhcm1zIGZyZXF1ZW5jeSkNCiAgc2VsZWN0KElELCB0b3RhbF9mcmVxMV8yKSAlPiUgIyByZXN0cmljdCBpbnRlcmVzdCBjb2x1bW5zDQogIGRpc3RpbmN0KElELCB0b3RhbF9mcmVxMV8yKSAlPiUgIyBmaWx0ZXIgdW5pcXVlIGVudHJpZXMNCiAgYXJyYW5nZSgtdG90YWxfZnJlcTFfMikgJT4lDQogIHVuZ3JvdXAoKSAlPiUgIyBvcmRlciBkYXRhc2V0IGJ5IGRlc2NlbmRpbmcgb3JkZXINCiAgbXV0YXRlKHRvdGFsX2ZyZXExXzJfcGVyYyA9IDEwMCAqICh0b3RhbF9mcmVxMV8yIC8gc3VtKHRvdGFsX2ZyZXExXzIpKSkNCnRvdGFsIDwtIGMoIlRvdGFsIiwgc3VtKGZyZXEkdG90YWxfZnJlcTFfMiksIHN1bShmcmVxJHRvdGFsX2ZyZXExXzJfcGVyYykpDQpmcmVxIDwtIHJiaW5kKGZyZXEsIHRvdGFsKQ0KZnJlcSA8LSBmcmVxICU+JQ0KICBtdXRhdGUodG90YWxfZnJlcTFfMiA9IGFzLm51bWVyaWModG90YWxfZnJlcTFfMikpICU+JQ0KICBtdXRhdGUodG90YWxfZnJlcTFfMl9wZXJjID0gYXMubnVtZXJpYyh0b3RhbF9mcmVxMV8yX3BlcmMpKQ0KDQoNCnBsb3QxIDwtIHVwdGltZV9jb21wb25lbnRzICU+JQ0KICBhcnJhbmdlKGRlc2MoZG93bnRpbWUpKSAlPiUNCiAgc2xpY2VfaGVhZChuID0gMjApICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKElELCAtZG93bnRpbWUpLCB5ID0gZG93bnRpbWUsIGZpbGwgPSBJRCkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBhc3RlMChyb3VuZChkb3dudGltZSwgMSksICIlIikpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAyLjUpICsNCiAgbGFicyh4ID0gIkRldmljZSBJRCIsIHkgPSAiRG93bnRpbWUgKCUpIiwgdGl0bGUgPSAiMjAgRGV2aWNlcyB3aXRoIGhpZ2hlc3QgZG93bnRpbWUiKSArDQogIHRoZW1lX2NsZWFuKCkgKw0KICB0aGVtZSgNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSwNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSINCiAgKQ0KDQoNCnBsb3QyIDwtIGdncGxvdChmcmVxWzE6MjAsIF0sIGFlcyh4ID0gcmVvcmRlcihJRCwgLXRvdGFsX2ZyZXExXzIpLCB5ID0gdG90YWxfZnJlcTFfMiwgZmlsbCA9IElEKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUwKHJvdW5kKHRvdGFsX2ZyZXExXzJfcGVyYywgMSksICIlIikpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAyLjUpICsNCiAgbGFicyh4ID0gIkRldmljZSBJRCIsIHkgPSAiQ291bnQgPSAjQWxhcm1zIDEgKyAjQWxhcm1zIDIiLCB0aXRsZSA9ICIyMCBEZXZpY2VzICh1cHRpbWUgPDk3JSkgd2l0aCBoaWdoZXN0IG5yLiBvZiBhbGFybXMiKSArDQogIHRoZW1lX2NsZWFuKCkgKw0KICB0aGVtZSgNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSwNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSINCiAgKQ0KDQpwbG90MSArIHBsb3QyDQpgYGANCg0KIyMjIyBBIGhlYXRtYXAtbGlrZSBwbG90DQoNClRoaXMgcGxvdCBzaG93cyB0aGUgbW9zdCBjb21tb24gdHdvIGFsYXJtcyBieSBkZXZpY2UgSUQuIDxicj4gSXQgYWxsb3dzDQp0byBpZGVudGlmeSBmb3IgZWFjaCBkZXZpY2UgaXRzIG1vc3QgY29tbW9uIGFsYXJtLCBhbGxvd2luZyBkaXJlY3QNCmludGVydmVudGlvbi48YnI+IEluIHJlZCBpcyB0aGUgbW9zdCBmcmVxdWVudCBhbGFybSBwZXIgZGV2aWNlLCBpbg0KeWVsbG93IGlzIHRoZSBzZWNvbmQgbW9zdCBmcmVxdWVudCBhbGFybS4NCg0KYGBge3IsIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsIG91dC53aWR0aCA9ICIxMi41aW4ifQ0KIyBUaGlzIGNodW5rIGNvbnRhaW5zIHRyYW5zZm9ybWF0aW9ucyBuZWNlc3NhcnkgdG8gcGxvdCBhIGhlYXRtYS1saWtlIHBsb3QNCiMgQ29udmVydCByZWxldmFudCBjb2x1bW5zIHRvIGZhY3RvcnMNCm1lcmdlZCRJRCA8LSBmYWN0b3IobWVyZ2VkJElEKQ0KbWVyZ2VkJHRvcF9hbGFybXMgPC0gZmFjdG9yKG1lcmdlZCR0b3BfYWxhcm1zKQ0KDQoNCiMgQ3JlYXRlIHByZXNlbmNlIG1hdHJpeA0KcHJlc2VuY2VfbWF0cml4IDwtIHRhYmxlKG1lcmdlZCRJRCwgbWVyZ2VkJHRvcF9hbGFybXMpDQoNCiMgQ29udmVydCB0aGUgcHJlc2VuY2UgbWF0cml4IHRvIGEgZGF0YSBmcmFtZQ0KcHJlc2VuY2VfZGYgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgocHJlc2VuY2VfbWF0cml4KQ0KDQojIFJlc2hhcGUgZGF0YSBmb3IgZ2dwbG90DQpwcmVzZW5jZV9kZiA8LSBwcmVzZW5jZV9kZiAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJJRCIpICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IC1JRCwgbmFtZXNfdG8gPSAidG9wX2FsYXJtcyIsIHZhbHVlc190byA9ICJQcmVzZW5jZSIpDQoNCnByZXNlbmNlX2RmIDwtIHByZXNlbmNlX2RmICU+JQ0KICBtdXRhdGUob3JkZXIgPSBpZmVsc2UoSUQgJWluJSBtZXJnZWQkSUQgJiB0b3BfYWxhcm1zICVpbiUgbWVyZ2VkJHRvcF9hbGFybXMsDQogICAgbWVyZ2VkJG9yZGVyW21hdGNoKHBhc3RlKElELCB0b3BfYWxhcm1zKSwgcGFzdGUobWVyZ2VkJElELCBtZXJnZWQkdG9wX2FsYXJtcykpXSwNCiAgICBOQQ0KICApKQ0KDQojIEVuc3VyZSAnUHJlc2VuY2UnIGlzIHRyZWF0ZWQgYXMgYSBmYWN0b3INCnByZXNlbmNlX2RmJFByZXNlbmNlIDwtIGZhY3RvcihwcmVzZW5jZV9kZiRQcmVzZW5jZSwgbGV2ZWxzID0gYygwLCAxKSwgbGFiZWxzID0gYygiQWJzZW50IiwgIlByZXNlbnQiKSkNCg0KDQoNCmEgPC0gZ2dwbG90KHByZXNlbmNlX2RmLCBhZXMoeCA9IElELCB5ID0gdG9wX2FsYXJtcywgZmlsbCA9IGlmZWxzZShpcy5uYShvcmRlciksICJOb3Qgb2JzZXJ2ZWQiLCBpZmVsc2Uob3JkZXIgPT0gIjEiLCAiVG9wMSBBbGFybSIsICJUb3AyIEFsYXJtIikpKSkgKw0KICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIk5vdCBvYnNlcnZlZCIgPSAid2hpdGUiLCAiVG9wMSBBbGFybSIgPSAicmVkIiwgIlRvcDIgQWxhcm0iID0gInllbGxvdyIpLCBuYW1lID0gIkFsYXJtcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh4ID0gIklEIiwgeSA9ICJUb3AgQWxhcm1zIiwgdGl0bGUgPSAiUHJlc2VuY2Ugb2YgVG9wIEFsYXJtcyBieSBJRCB3aGVuIHVwdGltZSA8IDk3JSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSwgc2l6ZSA9IDUpKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IHNlcSgwLjUsIG5yb3cocHJlc2VuY2VfZGYpICsgMC41LCBieSA9IDEpLCBjb2xvciA9ICJncmF5IiwgbGluZXR5cGUgPSAiZGFzaGVkIikNCg0KIyBDZW50ZXIgdGhlIHBsb3QNCg0KZ2dwbG90bHkoYSkNCmBgYA0KDQojIyMgV2hhdCBhc3NvY2lhdGVzIG1vc3RseSB0byBBbGFybTE/DQoNCkFsYXJtMSBpcyB0aGUgbW9zdCBmcmVxdWVudCB0eXBlIG9mIGFsYXJtLiBBcyBhIGZpcnN0IHN0ZXAgaW4gdGhlDQpkaXJlY3Rpb24gb2YgYSBwcmVkaWN0aXZlIG1haW50ZW5hbmNlIG1vZGVsLCBpdCBtaWdodCBiZSBpbnRlcmVzdGluZw0KdW5kZXJzdGFuZGluZyB3aGljaCBhcmUgdGhlIHZhcmlhYmxlcyBhc3NvY2lhdGVkIHRoZSBtb3N0IHRvIGl0cw0KbWFuaWZlc3RhdGlvbi48YnI+IDxiPiBUaGlzIGlzIGEgdmVyeSByYXcgYW5kIHByZWxpbWluYXJ5IGF0dGVtcHQuIDwvYj4NCg0KIyMjIyBCb3J1dGEgJiBmZWF0dXJlcyBpbXBvcnRhbmNlOiBhbm90aGVyIHdheSB0byBsb29rIGF0IGNvcnJlbGF0aW9ucw0KDQpTaW1wbHkgcHV0LCBCb3J1dGEgdmFyaWFibGVzJyBpbXBvcnRhbmNlIGV4cGxhaW5zIHRoZSBkZWdyZWUgdG8gd2hpY2ggYW4NCmlucHV0IGNvbnRyaWJ1dGVzIHRvIHRoZSBwcmVkaWN0aW9uIG9mIHRoZSBvdXRwdXQuIE1vcmUgaW1wb3J0YW50DQp2YXJpYWJsZXMgYXJlIHRodXMgbW9yZSByZWxhdGVkIHRvIG90aGUgb3V0cHV0Ljxicj4gQXQgYSBmaXJzdCBzaWdodCwNCnRoZSAoYmF0dGVyeSkgU3RhdGUgb2YgQ2hhcmdlIGlzIHRoZSBtb3N0IGltcGFjdGZ1bCB2YXJpYWJsZSBvbiB0aGUNCm1hbmlmZXN0YXRpb24gb2YgQWxhcm1zMS4NCg0KYGBge3IsIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gOCwgb3V0LndpZHRoID0gIjguNWluIn0NCmJvcnV0YV9kYXRhPC1kYXRhDQojRHJvcCBOQQ0KYm9ydXRhX2RhdGE8LSBuYS5vbWl0KGJvcnV0YV9kYXRhKQ0KDQojVHJhbnNmb3JtIGFsYXJtMSBpbnRvIGEgYmluYXJ5IHZhcmlhYmxlOiBJZiA9IE5PIEFMQVJNOyAhPSAwIGFueSBvdGhlciBhbGFybQ0KYm9ydXRhX2RhdGE8LWJvcnV0YV9kYXRhJT4lDQogIG11dGF0ZShBbGFybXMxID0gYXMuZmFjdG9yKGlmZWxzZShBbGFybXMxID09IDAsIDAsIDEpKSwNCiAgICAgICAgIE1haW5TdGF0ZSA9IGFzLmZhY3RvcihNYWluU3RhdGUpLA0KICAgICAgICAgU3RhdGVPZkhlYWx0aCA9IGFzLmZhY3RvcihTdGF0ZU9mSGVhbHRoKSwNCiAgICAgICAgIFNhZmVQb3NpdGlvblN0YXRlID0gYXMuZmFjdG9yKFNhZmVQb3NpdGlvblN0YXRlKQ0KICApDQogICAgICAgICANCg0KI1NlbGVjdCByZWxldmFudCB2YXJpYWFibGUgZm9yIGlucHV0cw0KYm9ydXRhX2RhdGE8LWJvcnV0YV9kYXRhWyxjKDYsODpkaW0oYm9ydXRhX2RhdGEpWzJdKV0NCiNCb3J1dGEgc2VsZWN0b3INCnNldC5zZWVkKDEpDQpib3J1dGEgPC0gQm9ydXRhKGJvcnV0YV9kYXRhJEFsYXJtczEgfiAuICxkYXRhPSBib3J1dGFfZGF0YSwgIGRvVHJhY2UgPSAyLCBtYXhSdW5zID0gMTAwKQ0KYm9ydXRhIDwtIHBsb3QoYm9ydXRhLCBsYXMgPSAyLCBjZXguYXhpcyA9IDAuNSwgeGxhYiA9ICIiKQ0KYm9ydXRhDQpgYGANCg0K